library(tidyverse)
library(lubridate)
library(tsibble)
library(tsibbledata)
library(fable)
library(nycflights13)

1 Question 1 Load in the nyc_bikes data from the tsibbledata package. Have an initial look at it to see what you’re working with. Create three new columns: one that stores only the year the bike was used, one that stores only the month the bike was used, and one that stores the date. Use the data stored in start_time to create these new columns.


nyc_bikes

codeclan solution

nyc_bikes_year <- nyc_bikes %>%
  select(-gender, -type, -birth_year) %>%
  mutate(year_of_use = year(start_time),
         month_of_use = month(start_time, label = TRUE), 
         date_of_use = date(start_time)
  )

head(nyc_bikes_year,3)
NA

2 Question 2 Summarise the number of bike hire counts by month. Make a plot of this data. *Hint: remember that to group time series (tsibble) data, you need to use index_by instead of group_by before your summarise function. What does this plot tell you about the time series? Do you think this downsampled data would be adequate to build a forecast with?

nyc_count <- nyc_bikes_time %>%
  group_by_key(bike_id) %>%
  index_by(month) %>%
  summarise(bike_hire_counts = n())
ggplot(data = nyc_count, aes(x = month, y = bike_hire_counts, group = bike_id)) +
  geom_line(aes(color=bike_id))

Codeclan Solution

nyc_bikes_month_summary <- nyc_bikes_year %>% 
  index_by(month_of_use) %>%
  summarise(bike_hire_counts = n())

ggplot(nyc_bikes_month_summary) + 
  aes(x = month_of_use, y = bike_hire_counts) +
  geom_point() + 
  geom_line(group = 1) +              
  ggtitle("Bike hire in NYC") + 
  xlab("Month") + ylab ("total count")

3 Question 3 Now Summarise the number of bike hire counts by date. Make a plot of this new aggregated data. What does this plot tell you about the time series? Would this data be preferrable for time series forecasting compared to the monthly data?

nyc_bikes_date <- nyc_bikes_time %>% 
  index_by(date) %>%
  summarise(bike_hire_counts = n())

nyc_bikes_date 
ggplot(data = nyc_bikes_date, aes(x = date, y = bike_hire_counts)) +
  geom_line()

NA
NA

CODECLAN SOLUTION

# count by date 
nyc_bikes_date_summary <- nyc_bikes_year %>% 
  index_by(date_of_use) %>%
  summarise(bike_hire_counts = n())

nyc_bikes_date_summary
ggplot(nyc_bikes_date_summary) + 
  aes(x = date_of_use, y = bike_hire_counts) +
  geom_point() + 
  geom_line(group = 1) +              
  ggtitle("Bike hire in NYC") + 
  xlab("Date") + ylab ("total count")

Let’s go with our date one. The reasons for this is as follows:

  1. Forecasts work better with more data. The monthly data didn’t have that many data points.
  2. There will be daily variation, and monthly variation that will be more accurately captured by using date based data.

4 Question 4

Let’s begin to build a model. For this, we will use the downsampled by date dataset we created above in question 3. If you haven’t managed to get the code to do this, you can find it below.


count <- nyc_bikes_date %>% 
  select(date, bike_hire_counts)

count %>%
  autoplot()
Plot variable not specified, automatically selected `.vars = bike_hire_counts`

fit_bikes <- nyc_bikes_date %>%
    fill_gaps() %>%
    model(
    snaive = SNAIVE(count),
    mean_model = MEAN(count),
    arima = ARIMA(count)
  )
argument is not numeric or logical: returning NA1 error encountered for snaive
[1] invalid time series parameters specified
1 error encountered for mean_model
[1] (list) object cannot be coerced to type 'double'
1 error encountered for arima
[1] 'x' must be a vector of an atomic type
# fill the gaps : here i'll use median as it's a decent summary of how bike rentals work (not overly affected by seasonal changes)
nyc_bikes_filled <- nyc_bikes_date %>%
  fill_gaps(bike_hire_counts = as.integer(median(bike_hire_counts)))

fit_bikes <- nyc_bikes_filled %>%
    model(
    snaive = SNAIVE(count),
    mean_model = MEAN(count),
    arima = ARIMA(count)
  )
argument is not numeric or logical: returning NA1 error encountered for snaive
[1] invalid time series parameters specified
1 error encountered for mean_model
[1] (list) object cannot be coerced to type 'double'
1 error encountered for arima
[1] 'x' must be a vector of an atomic type

CODECLAN SOLUTION

# count by date 
nyc_bikes_date_summary <- nyc_bikes_year %>% 
  index_by(date_of_use) %>%
  summarise(bike_hire_counts = n())

nyc_bikes_date_summary
# fill the gaps : here i'll use median as it's a decent summary of how bike rentals work (not overly affected by seasonal changes)
nyc_bikes_filled <- nyc_bikes_date_summary %>%
  fill_gaps(bike_hire_counts = as.integer(median(bike_hire_counts)))

# build a model
bikes_fit <- nyc_bikes_filled %>%
  model(
    naive = NAIVE(bike_hire_counts), 
    mean_model = MEAN(bike_hire_counts), 
    snaive = SNAIVE(bike_hire_counts)
  )

5 Question 5 Now we have our model fit, build a forecast to predict bike use over the next four months. Plot your models alongside your data. Hint: forecast parameter would be roughly 120 (30 days x 4 months)

# build a forecast
bikes_forecast <- bikes_fit %>%
  fabletools::forecast(h = 30*4)
bikes_forecast
bikes_forecast %>%
  autoplot(nyc_bikes_filled, level = NULL) 

# create a shorter interval to plot: years after 1980
bikes_shorter <- nyc_bikes_filled %>%
  mutate(month = month(date_of_use)) %>%
  filter(month >= 8)

# check one model at a time
bikes_forecast %>%
  filter(.model == "snaive") %>%
  autoplot(bikes_shorter, level = NULL) 

6 Question 6 Test your model accuracy : choose a training data set from your main dataset, build a forecast on the training set, and then plot the training set forecast against the real data. Calculate model accuracy.


## test model accuracy 
# Set training data from Jan to Nov
train <- nyc_bikes_filled %>%
  mutate(month = month(date_of_use)) %>%
  filter(month <= 10)

# run the model on the training set 
bikes_fit_test <- train %>%
  model(
    naive = NAIVE(bike_hire_counts), 
    mean_model = MEAN(bike_hire_counts), 
    snaive = SNAIVE(bike_hire_counts))

bikes_fit_test
# forecast from the training set
bikes_forecast_test <- bikes_fit_test %>%
  fabletools::forecast(h = 12*6)

# Plot forecasts against actual values
bikes_forecast_test %>%
  autoplot(train, level = NULL) + 
  autolayer(nyc_bikes_filled, color = "black")
Plot variable not specified, automatically selected `.vars = bike_hire_counts`

# note accuracy, arrange by best
accuracy_model <- fabletools::accuracy(bikes_forecast_test, nyc_bikes_filled)
The future dataset is incomplete, incomplete out-of-sample data will be treated as missing. 
11 observations are missing between 2019-01-01 and 2019-01-11
accuracy_model %>% 
  select(-.type) %>%
  arrange(RMSE)
NA

7 Question 7 Look at your forecast plots and accuracy values. Describe your results. Are your models a good fit for the data? If not, why not? What would you suggest doing with the data if you were expected to present these back to a client? For example, would you ask for more data? Would you test a different model?

SUMMARISE YOUR FINDINGS

Forecast isn’t great. This could be for several reasons:

  1. not enough data
  2. something is affecting it
  3. wrong model choice
  4. I’d collect more data if possible - we only have a year’s worth so far.

8 Question 8 Make a simple ggplot (geom_point) which plots the start longitude and latitudes of each bike. Create a separate facet for each bike_id. Colour the dots in by month of use. What does this tell you about what month each bike was used most in?

Do the same for the end longitude and latitudes.

# make the month 
nyc_bikes_spatial <-nyc_bikes %>%
  select(bike_id, start_time, start_lat, start_long, end_lat, end_long) %>%
  mutate(month = month(start_time, label = TRUE))

# make a faceted plot : start points
ggplot(nyc_bikes_spatial) + 
  geom_point(aes(x=start_lat, y = start_long, colour = month)) +
  facet_wrap(~bike_id) +
  ggtitle("NYC bikes: start points") +
  xlab("Latitude") + ylab("Longitude")


# make a faceted plot : end points
ggplot(nyc_bikes_spatial) + 
  geom_point(aes(x=end_lat, y = end_long, colour = month)) +
  facet_wrap(~bike_id) +
  ggtitle("NYC bikes : end points") +
  xlab("Latitude") + ylab("Longitude")

9 Question 9 Create an interactive leaflet plot which plots the start points of the city bikes. Ensure it has at least markers to denote start points (taken from the nyc_bikes_spatial data). Feel free to add any additional features you wish.

# make a leaflet plot
library(leaflet)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
# create leaflet map
leaflet(nyc_bikes_spatial) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~start_long,
                   lat = ~start_lat) %>%
    setView(-74.00, 40.71, zoom = 12) %>%
  addProviderTiles("CartoDB.Positron")

NA

10 Extension This is VERY much an extension task, and will take a bit of time (as well as googling) if you decide to attempt it.

Adapt your leaflet plot which adds lines showing the start and end point of each bike

install.packages("geosphere")
trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/geosphere_1.5-10.tgz'
Content type 'application/x-gzip' length 1003838 bytes (980 KB)
==================================================
downloaded 980 KB

The downloaded binary packages are in
    /var/folders/58/m5fvfpw93rz5rtzg6nc5_lbr0000gn/T//Rtmpl1CVEN/downloaded_packages

# load the geosphere package to add intermediate points (lines)
library(geosphere)

# subset the data to make it easier
bikes <- nyc_bikes %>%
  select(bike_id, start_time, start_lat, start_long, end_lat, end_long)

# get start points (longitude has to go first)
start <- bikes %>%
  select(start_long, start_lat)
end <- bikes %>% 
  select(end_long, end_lat)
# convert to matrix  
start_matrix <- as.matrix(start[ , c(1,2)])
end_matrix <- as.matrix(end[ , c(1,2)])

# plot using leaflet
gcIntermediate(start_matrix, end_matrix,  
           n=100, 
           addStartEnd=TRUE,
           sp=TRUE) %>% 
leaflet() %>% 
addTiles() %>% 
addPolylines() 

NA
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh0c2liYmxlKQpsaWJyYXJ5KHRzaWJibGVkYXRhKQpsaWJyYXJ5KGZhYmxlKQoKCmBgYAoKMSBRdWVzdGlvbiAxCkxvYWQgaW4gdGhlIG55Y19iaWtlcyBkYXRhIGZyb20gdGhlIHRzaWJibGVkYXRhIHBhY2thZ2UuIEhhdmUgYW4gaW5pdGlhbCBsb29rIGF0IGl0IHRvIHNlZSB3aGF0IHlvdeKAmXJlIHdvcmtpbmcgd2l0aC4gQ3JlYXRlIHRocmVlIG5ldyBjb2x1bW5zOiBvbmUgdGhhdCBzdG9yZXMgb25seSB0aGUgeWVhciB0aGUgYmlrZSB3YXMgdXNlZCwgb25lIHRoYXQgc3RvcmVzIG9ubHkgdGhlIG1vbnRoIHRoZSBiaWtlIHdhcyB1c2VkLCBhbmQgb25lIHRoYXQgc3RvcmVzIHRoZSBkYXRlLiBVc2UgdGhlIGRhdGEgc3RvcmVkIGluIHN0YXJ0X3RpbWUgdG8gY3JlYXRlIHRoZXNlIG5ldyBjb2x1bW5zLgoKYGBge3J9CgpueWNfYmlrZXMKYGBgCgpgYGB7cn0KbnljX2Jpa2VzX3RpbWUgPC0gbnljX2Jpa2VzICU+JQogIG11dGF0ZSh5ZWFyID0geWVhcihzdGFydF90aW1lKSwKICAgICAgICAgbW9udGggPSBtb250aChzdGFydF90aW1lKSwKICAgICAgICAgZGF0ZSA9IGFzX2RhdGUoc3RhcnRfdGltZSkpCgpueWNfYmlrZXNfdGltZQpgYGAKCmNvZGVjbGFuIHNvbHV0aW9uCgpgYGB7cn0KbnljX2Jpa2VzX3llYXIgPC0gbnljX2Jpa2VzICU+JQogIHNlbGVjdCgtZ2VuZGVyLCAtdHlwZSwgLWJpcnRoX3llYXIpICU+JQogIG11dGF0ZSh5ZWFyX29mX3VzZSA9IHllYXIoc3RhcnRfdGltZSksCiAgICAgICAgIG1vbnRoX29mX3VzZSA9IG1vbnRoKHN0YXJ0X3RpbWUsIGxhYmVsID0gVFJVRSksIAogICAgICAgICBkYXRlX29mX3VzZSA9IGRhdGUoc3RhcnRfdGltZSkKICApCgpoZWFkKG55Y19iaWtlc195ZWFyLDMpCgpgYGAKCgoyIFF1ZXN0aW9uIDIKU3VtbWFyaXNlIHRoZSBudW1iZXIgb2YgYmlrZSBoaXJlIGNvdW50cyBieSBtb250aC4gTWFrZSBhIHBsb3Qgb2YgdGhpcyBkYXRhLiAqSGludDogcmVtZW1iZXIgdGhhdCB0byBncm91cCB0aW1lIHNlcmllcyAodHNpYmJsZSkgZGF0YSwgeW91IG5lZWQgdG8gdXNlIGluZGV4X2J5IGluc3RlYWQgb2YgZ3JvdXBfYnkgYmVmb3JlIHlvdXIgc3VtbWFyaXNlIGZ1bmN0aW9uLiBXaGF0IGRvZXMgdGhpcyBwbG90IHRlbGwgeW91IGFib3V0IHRoZSB0aW1lIHNlcmllcz8gRG8geW91IHRoaW5rIHRoaXMgZG93bnNhbXBsZWQgZGF0YSB3b3VsZCBiZSBhZGVxdWF0ZSB0byBidWlsZCBhIGZvcmVjYXN0IHdpdGg/CgpgYGB7cn0KbnljX2NvdW50IDwtIG55Y19iaWtlc190aW1lICU+JQogIGdyb3VwX2J5X2tleShiaWtlX2lkKSAlPiUKICBpbmRleF9ieShtb250aCkgJT4lCiAgc3VtbWFyaXNlKGJpa2VfaGlyZV9jb3VudHMgPSBuKCkpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG55Y19jb3VudCwgYWVzKHggPSBtb250aCwgeSA9IGJpa2VfaGlyZV9jb3VudHMsIGdyb3VwID0gYmlrZV9pZCkpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yPWJpa2VfaWQpKQoKYGBgCgpDb2RlY2xhbiBTb2x1dGlvbgoKYGBge3J9Cm55Y19iaWtlc19tb250aF9zdW1tYXJ5IDwtIG55Y19iaWtlc195ZWFyICU+JSAKICBpbmRleF9ieShtb250aF9vZl91c2UpICU+JQogIHN1bW1hcmlzZShiaWtlX2hpcmVfY291bnRzID0gbigpKQoKZ2dwbG90KG55Y19iaWtlc19tb250aF9zdW1tYXJ5KSArIAogIGFlcyh4ID0gbW9udGhfb2ZfdXNlLCB5ID0gYmlrZV9oaXJlX2NvdW50cykgKwogIGdlb21fcG9pbnQoKSArIAogIGdlb21fbGluZShncm91cCA9IDEpICsgICAgICAgICAgICAgIAogIGdndGl0bGUoIkJpa2UgaGlyZSBpbiBOWUMiKSArIAogIHhsYWIoIk1vbnRoIikgKyB5bGFiICgidG90YWwgY291bnQiKQoKYGBgCgogCgozIFF1ZXN0aW9uIDMKTm93IFN1bW1hcmlzZSB0aGUgbnVtYmVyIG9mIGJpa2UgaGlyZSBjb3VudHMgYnkgZGF0ZS4gTWFrZSBhIHBsb3Qgb2YgdGhpcyBuZXcgYWdncmVnYXRlZCBkYXRhLiBXaGF0IGRvZXMgdGhpcyBwbG90IHRlbGwgeW91IGFib3V0IHRoZSB0aW1lIHNlcmllcz8gV291bGQgdGhpcyBkYXRhIGJlIHByZWZlcnJhYmxlIGZvciB0aW1lIHNlcmllcyBmb3JlY2FzdGluZyBjb21wYXJlZCB0byB0aGUgbW9udGhseSBkYXRhPwoKYGBge3J9Cm55Y19iaWtlc19kYXRlIDwtIG55Y19iaWtlc190aW1lICU+JSAKICBpbmRleF9ieShkYXRlKSAlPiUKICBzdW1tYXJpc2UoYmlrZV9oaXJlX2NvdW50cyA9IG4oKSkKCm55Y19iaWtlc19kYXRlIApgYGAKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBueWNfYmlrZXNfZGF0ZSwgYWVzKHggPSBkYXRlLCB5ID0gYmlrZV9oaXJlX2NvdW50cykpICsKICBnZW9tX2xpbmUoKQoKCmBgYAoKQ09ERUNMQU4gU09MVVRJT04KCmBgYHtyfQojIGNvdW50IGJ5IGRhdGUgCm55Y19iaWtlc19kYXRlX3N1bW1hcnkgPC0gbnljX2Jpa2VzX3llYXIgJT4lIAogIGluZGV4X2J5KGRhdGVfb2ZfdXNlKSAlPiUKICBzdW1tYXJpc2UoYmlrZV9oaXJlX2NvdW50cyA9IG4oKSkKCm55Y19iaWtlc19kYXRlX3N1bW1hcnkKYGBgCgoKYGBge3J9CmdncGxvdChueWNfYmlrZXNfZGF0ZV9zdW1tYXJ5KSArIAogIGFlcyh4ID0gZGF0ZV9vZl91c2UsIHkgPSBiaWtlX2hpcmVfY291bnRzKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9saW5lKGdyb3VwID0gMSkgKyAgICAgICAgICAgICAgCiAgZ2d0aXRsZSgiQmlrZSBoaXJlIGluIE5ZQyIpICsgCiAgeGxhYigiRGF0ZSIpICsgeWxhYiAoInRvdGFsIGNvdW50IikKCmBgYAoKCkxldOKAmXMgZ28gd2l0aCBvdXIgZGF0ZSBvbmUuIFRoZSByZWFzb25zIGZvciB0aGlzIGlzIGFzIGZvbGxvd3M6CgoxLiBGb3JlY2FzdHMgd29yayBiZXR0ZXIgd2l0aCBtb3JlIGRhdGEuIFRoZSBtb250aGx5IGRhdGEgZGlkbuKAmXQgaGF2ZSB0aGF0IG1hbnkgZGF0YSBwb2ludHMuCjIuIFRoZXJlIHdpbGwgYmUgZGFpbHkgdmFyaWF0aW9uLCBhbmQgbW9udGhseSB2YXJpYXRpb24gdGhhdCB3aWxsIGJlIG1vcmUgYWNjdXJhdGVseSBjYXB0dXJlZCBieSB1c2luZyAgICBkYXRlIGJhc2VkIGRhdGEuCgoKNCBRdWVzdGlvbiA0CgpMZXTigJlzIGJlZ2luIHRvIGJ1aWxkIGEgbW9kZWwuIEZvciB0aGlzLCB3ZSB3aWxsIHVzZSB0aGUgZG93bnNhbXBsZWQgYnkgZGF0ZSBkYXRhc2V0IHdlIGNyZWF0ZWQgYWJvdmUgaW4gcXVlc3Rpb24gMy4gSWYgeW91IGhhdmVu4oCZdCBtYW5hZ2VkIHRvIGdldCB0aGUgY29kZSB0byBkbyB0aGlzLCB5b3UgY2FuIGZpbmQgaXQgYmVsb3cuCgpgYGB7cn0KCmNvdW50IDwtIG55Y19iaWtlc19kYXRlICU+JSAKICBzZWxlY3QoZGF0ZSwgYmlrZV9oaXJlX2NvdW50cykKCmNvdW50ICU+JQogIGF1dG9wbG90KCkKYGBgCgpgYGB7cn0KZml0X2Jpa2VzIDwtIG55Y19iaWtlc19kYXRlICU+JQogICAgZmlsbF9nYXBzKCkgJT4lCiAgICBtb2RlbCgKICAgIHNuYWl2ZSA9IFNOQUlWRShjb3VudCksCiAgICBtZWFuX21vZGVsID0gTUVBTihjb3VudCksCiAgICBhcmltYSA9IEFSSU1BKGNvdW50KQogICkKCmBgYAoKCgpgYGB7cn0KIyBmaWxsIHRoZSBnYXBzIDogaGVyZSBpJ2xsIHVzZSBtZWRpYW4gYXMgaXQncyBhIGRlY2VudCBzdW1tYXJ5IG9mIGhvdyBiaWtlIHJlbnRhbHMgd29yayAobm90IG92ZXJseSBhZmZlY3RlZCBieSBzZWFzb25hbCBjaGFuZ2VzKQpueWNfYmlrZXNfZmlsbGVkIDwtIG55Y19iaWtlc19kYXRlICU+JQogIGZpbGxfZ2FwcyhiaWtlX2hpcmVfY291bnRzID0gYXMuaW50ZWdlcihtZWRpYW4oYmlrZV9oaXJlX2NvdW50cykpKQoKYGBgCgoKYGBge3J9CgpmaXRfYmlrZXMgPC0gbnljX2Jpa2VzX2ZpbGxlZCAlPiUKICAgIG1vZGVsKAogICAgc25haXZlID0gU05BSVZFKGNvdW50KSwKICAgIG1lYW5fbW9kZWwgPSBNRUFOKGNvdW50KSwKICAgIGFyaW1hID0gQVJJTUEoY291bnQpCiAgKQoKYGBgCgpDT0RFQ0xBTiBTT0xVVElPTgoKYGBge3J9CiMgY291bnQgYnkgZGF0ZSAKbnljX2Jpa2VzX2RhdGVfc3VtbWFyeSA8LSBueWNfYmlrZXNfeWVhciAlPiUgCiAgaW5kZXhfYnkoZGF0ZV9vZl91c2UpICU+JQogIHN1bW1hcmlzZShiaWtlX2hpcmVfY291bnRzID0gbigpKQoKbnljX2Jpa2VzX2RhdGVfc3VtbWFyeQpgYGAKCgpgYGB7cn0KIyBmaWxsIHRoZSBnYXBzIDogaGVyZSBpJ2xsIHVzZSBtZWRpYW4gYXMgaXQncyBhIGRlY2VudCBzdW1tYXJ5IG9mIGhvdyBiaWtlIHJlbnRhbHMgd29yayAobm90IG92ZXJseSBhZmZlY3RlZCBieSBzZWFzb25hbCBjaGFuZ2VzKQpueWNfYmlrZXNfZmlsbGVkIDwtIG55Y19iaWtlc19kYXRlX3N1bW1hcnkgJT4lCiAgZmlsbF9nYXBzKGJpa2VfaGlyZV9jb3VudHMgPSBhcy5pbnRlZ2VyKG1lZGlhbihiaWtlX2hpcmVfY291bnRzKSkpCgojIGJ1aWxkIGEgbW9kZWwKYmlrZXNfZml0IDwtIG55Y19iaWtlc19maWxsZWQgJT4lCiAgbW9kZWwoCiAgICBuYWl2ZSA9IE5BSVZFKGJpa2VfaGlyZV9jb3VudHMpLCAKICAgIG1lYW5fbW9kZWwgPSBNRUFOKGJpa2VfaGlyZV9jb3VudHMpLCAKICAgIHNuYWl2ZSA9IFNOQUlWRShiaWtlX2hpcmVfY291bnRzKQogICkKCmBgYAoKNSBRdWVzdGlvbiA1Ck5vdyB3ZSBoYXZlIG91ciBtb2RlbCBmaXQsIGJ1aWxkIGEgZm9yZWNhc3QgdG8gcHJlZGljdCBiaWtlIHVzZSBvdmVyIHRoZSBuZXh0IGZvdXIgbW9udGhzLiBQbG90IHlvdXIgbW9kZWxzIGFsb25nc2lkZSB5b3VyIGRhdGEuCkhpbnQ6IGZvcmVjYXN0IHBhcmFtZXRlciB3b3VsZCBiZSByb3VnaGx5IDEyMCAoMzAgZGF5cyB4IDQgbW9udGhzKQoKYGBge3J9CiMgYnVpbGQgYSBmb3JlY2FzdApiaWtlc19mb3JlY2FzdCA8LSBiaWtlc19maXQgJT4lCiAgZmFibGV0b29sczo6Zm9yZWNhc3QoaCA9IDMwKjQpCmJpa2VzX2ZvcmVjYXN0CmBgYAoKCmBgYHtyfQpiaWtlc19mb3JlY2FzdCAlPiUKICBhdXRvcGxvdChueWNfYmlrZXNfZmlsbGVkLCBsZXZlbCA9IE5VTEwpIAoKYGBgCgoKYGBge3J9CiMgY3JlYXRlIGEgc2hvcnRlciBpbnRlcnZhbCB0byBwbG90OiB5ZWFycyBhZnRlciAxOTgwCmJpa2VzX3Nob3J0ZXIgPC0gbnljX2Jpa2VzX2ZpbGxlZCAlPiUKICBtdXRhdGUobW9udGggPSBtb250aChkYXRlX29mX3VzZSkpICU+JQogIGZpbHRlcihtb250aCA+PSA4KQoKIyBjaGVjayBvbmUgbW9kZWwgYXQgYSB0aW1lCmJpa2VzX2ZvcmVjYXN0ICU+JQogIGZpbHRlcigubW9kZWwgPT0gInNuYWl2ZSIpICU+JQogIGF1dG9wbG90KGJpa2VzX3Nob3J0ZXIsIGxldmVsID0gTlVMTCkgCgpgYGAKCgo2IFF1ZXN0aW9uIDYKVGVzdCB5b3VyIG1vZGVsIGFjY3VyYWN5IDogY2hvb3NlIGEgdHJhaW5pbmcgZGF0YSBzZXQgZnJvbSB5b3VyIG1haW4gZGF0YXNldCwgYnVpbGQgYSBmb3JlY2FzdCBvbiB0aGUgdHJhaW5pbmcgc2V0LCBhbmQgdGhlbiBwbG90IHRoZSB0cmFpbmluZyBzZXQgZm9yZWNhc3QgYWdhaW5zdCB0aGUgcmVhbCBkYXRhLiBDYWxjdWxhdGUgbW9kZWwgYWNjdXJhY3kuCgpgYGB7cn0KCiMjIHRlc3QgbW9kZWwgYWNjdXJhY3kgCiMgU2V0IHRyYWluaW5nIGRhdGEgZnJvbSBKYW4gdG8gTm92CnRyYWluIDwtIG55Y19iaWtlc19maWxsZWQgJT4lCiAgbXV0YXRlKG1vbnRoID0gbW9udGgoZGF0ZV9vZl91c2UpKSAlPiUKICBmaWx0ZXIobW9udGggPD0gMTApCgojIHJ1biB0aGUgbW9kZWwgb24gdGhlIHRyYWluaW5nIHNldCAKYmlrZXNfZml0X3Rlc3QgPC0gdHJhaW4gJT4lCiAgbW9kZWwoCiAgICBuYWl2ZSA9IE5BSVZFKGJpa2VfaGlyZV9jb3VudHMpLCAKICAgIG1lYW5fbW9kZWwgPSBNRUFOKGJpa2VfaGlyZV9jb3VudHMpLCAKICAgIHNuYWl2ZSA9IFNOQUlWRShiaWtlX2hpcmVfY291bnRzKSkKCmJpa2VzX2ZpdF90ZXN0CmBgYAoKCmBgYHtyfQojIGZvcmVjYXN0IGZyb20gdGhlIHRyYWluaW5nIHNldApiaWtlc19mb3JlY2FzdF90ZXN0IDwtIGJpa2VzX2ZpdF90ZXN0ICU+JQogIGZhYmxldG9vbHM6OmZvcmVjYXN0KGggPSAxMio2KQoKIyBQbG90IGZvcmVjYXN0cyBhZ2FpbnN0IGFjdHVhbCB2YWx1ZXMKYmlrZXNfZm9yZWNhc3RfdGVzdCAlPiUKICBhdXRvcGxvdCh0cmFpbiwgbGV2ZWwgPSBOVUxMKSArIAogIGF1dG9sYXllcihueWNfYmlrZXNfZmlsbGVkLCBjb2xvciA9ICJibGFjayIpCmBgYAoKCmBgYHtyfQojIG5vdGUgYWNjdXJhY3ksIGFycmFuZ2UgYnkgYmVzdAphY2N1cmFjeV9tb2RlbCA8LSBmYWJsZXRvb2xzOjphY2N1cmFjeShiaWtlc19mb3JlY2FzdF90ZXN0LCBueWNfYmlrZXNfZmlsbGVkKQoKYGBgCgoKYGBge3J9CmFjY3VyYWN5X21vZGVsICU+JSAKICBzZWxlY3QoLS50eXBlKSAlPiUKICBhcnJhbmdlKFJNU0UpCgpgYGAKCgo3IFF1ZXN0aW9uIDcKTG9vayBhdCB5b3VyIGZvcmVjYXN0IHBsb3RzIGFuZCBhY2N1cmFjeSB2YWx1ZXMuIERlc2NyaWJlIHlvdXIgcmVzdWx0cy4gQXJlIHlvdXIgbW9kZWxzIGEgZ29vZCBmaXQgZm9yIHRoZSBkYXRhPyBJZiBub3QsIHdoeSBub3Q/IFdoYXQgd291bGQgeW91IHN1Z2dlc3QgZG9pbmcgd2l0aCB0aGUgZGF0YSBpZiB5b3Ugd2VyZSBleHBlY3RlZCB0byBwcmVzZW50IHRoZXNlIGJhY2sgdG8gYSBjbGllbnQ/IEZvciBleGFtcGxlLCB3b3VsZCB5b3UgYXNrIGZvciBtb3JlIGRhdGE/IFdvdWxkIHlvdSB0ZXN0IGEgZGlmZmVyZW50IG1vZGVsPwoKU1VNTUFSSVNFIFlPVVIgRklORElOR1MKCkZvcmVjYXN0IGlzbuKAmXQgZ3JlYXQuIFRoaXMgY291bGQgYmUgZm9yIHNldmVyYWwgcmVhc29uczoKCjEuIG5vdCBlbm91Z2ggZGF0YQoyLiBzb21ldGhpbmcgaXMgYWZmZWN0aW5nIGl0CjMuIHdyb25nIG1vZGVsIGNob2ljZQo0LiBJ4oCZZCBjb2xsZWN0IG1vcmUgZGF0YSBpZiBwb3NzaWJsZSAtIHdlIG9ubHkgaGF2ZSBhIHllYXLigJlzIHdvcnRoIHNvIGZhci4KCjggUXVlc3Rpb24gOApNYWtlIGEgc2ltcGxlIGdncGxvdCAoZ2VvbV9wb2ludCkgd2hpY2ggcGxvdHMgdGhlIHN0YXJ0IGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGVzIG9mIGVhY2ggYmlrZS4gQ3JlYXRlIGEgc2VwYXJhdGUgZmFjZXQgZm9yIGVhY2ggYmlrZV9pZC4gQ29sb3VyIHRoZSBkb3RzIGluIGJ5IG1vbnRoIG9mIHVzZS4gV2hhdCBkb2VzIHRoaXMgdGVsbCB5b3UgYWJvdXQgd2hhdCBtb250aCBlYWNoIGJpa2Ugd2FzIHVzZWQgbW9zdCBpbj8KCkRvIHRoZSBzYW1lIGZvciB0aGUgZW5kIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGVzLgoKYGBge3J9CiMgbWFrZSB0aGUgbW9udGggCm55Y19iaWtlc19zcGF0aWFsIDwtbnljX2Jpa2VzICU+JQogIHNlbGVjdChiaWtlX2lkLCBzdGFydF90aW1lLCBzdGFydF9sYXQsIHN0YXJ0X2xvbmcsIGVuZF9sYXQsIGVuZF9sb25nKSAlPiUKICBtdXRhdGUobW9udGggPSBtb250aChzdGFydF90aW1lLCBsYWJlbCA9IFRSVUUpKQoKIyBtYWtlIGEgZmFjZXRlZCBwbG90IDogc3RhcnQgcG9pbnRzCmdncGxvdChueWNfYmlrZXNfc3BhdGlhbCkgKyAKICBnZW9tX3BvaW50KGFlcyh4PXN0YXJ0X2xhdCwgeSA9IHN0YXJ0X2xvbmcsIGNvbG91ciA9IG1vbnRoKSkgKwogIGZhY2V0X3dyYXAofmJpa2VfaWQpICsKICBnZ3RpdGxlKCJOWUMgYmlrZXM6IHN0YXJ0IHBvaW50cyIpICsKICB4bGFiKCJMYXRpdHVkZSIpICsgeWxhYigiTG9uZ2l0dWRlIikKCmBgYAoKCmBgYHtyfQoKIyBtYWtlIGEgZmFjZXRlZCBwbG90IDogZW5kIHBvaW50cwpnZ3Bsb3QobnljX2Jpa2VzX3NwYXRpYWwpICsgCiAgZ2VvbV9wb2ludChhZXMoeD1lbmRfbGF0LCB5ID0gZW5kX2xvbmcsIGNvbG91ciA9IG1vbnRoKSkgKwogIGZhY2V0X3dyYXAofmJpa2VfaWQpICsKICBnZ3RpdGxlKCJOWUMgYmlrZXMgOiBlbmQgcG9pbnRzIikgKwogIHhsYWIoIkxhdGl0dWRlIikgKyB5bGFiKCJMb25naXR1ZGUiKQpgYGAKCjkgUXVlc3Rpb24gOQpDcmVhdGUgYW4gaW50ZXJhY3RpdmUgbGVhZmxldCBwbG90IHdoaWNoIHBsb3RzIHRoZSBzdGFydCBwb2ludHMgb2YgdGhlIGNpdHkgYmlrZXMuIEVuc3VyZSBpdCBoYXMgYXQgbGVhc3QgbWFya2VycyB0byBkZW5vdGUgc3RhcnQgcG9pbnRzICh0YWtlbiBmcm9tIHRoZSBueWNfYmlrZXNfc3BhdGlhbCBkYXRhKS4gRmVlbCBmcmVlIHRvIGFkZCBhbnkgYWRkaXRpb25hbCBmZWF0dXJlcyB5b3Ugd2lzaC4KCmBgYHtyfQojIG1ha2UgYSBsZWFmbGV0IHBsb3QKbGlicmFyeShsZWFmbGV0KQoKIyBjcmVhdGUgbGVhZmxldCBtYXAKbGVhZmxldChueWNfYmlrZXNfc3BhdGlhbCkgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGxuZyA9IH5zdGFydF9sb25nLAogICAgICAgICAgICAgICAgICAgbGF0ID0gfnN0YXJ0X2xhdCkgJT4lCiAgICBzZXRWaWV3KC03NC4wMCwgNDAuNzEsIHpvb20gPSAxMikgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpCgpgYGAKCgoKMTAgRXh0ZW5zaW9uClRoaXMgaXMgVkVSWSBtdWNoIGFuIGV4dGVuc2lvbiB0YXNrLCBhbmQgd2lsbCB0YWtlIGEgYml0IG9mIHRpbWUgKGFzIHdlbGwgYXMgZ29vZ2xpbmcpIGlmIHlvdSBkZWNpZGUgdG8gYXR0ZW1wdCBpdC4KCkFkYXB0IHlvdXIgbGVhZmxldCBwbG90IHdoaWNoIGFkZHMgbGluZXMgc2hvd2luZyB0aGUgc3RhcnQgYW5kIGVuZCBwb2ludCBvZiBlYWNoIGJpa2UKCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCJnZW9zcGhlcmUiKQoKYGBgCgoKYGBge3J9CgojIGxvYWQgdGhlIGdlb3NwaGVyZSBwYWNrYWdlIHRvIGFkZCBpbnRlcm1lZGlhdGUgcG9pbnRzIChsaW5lcykKbGlicmFyeShnZW9zcGhlcmUpCgojIHN1YnNldCB0aGUgZGF0YSB0byBtYWtlIGl0IGVhc2llcgpiaWtlcyA8LSBueWNfYmlrZXMgJT4lCiAgc2VsZWN0KGJpa2VfaWQsIHN0YXJ0X3RpbWUsIHN0YXJ0X2xhdCwgc3RhcnRfbG9uZywgZW5kX2xhdCwgZW5kX2xvbmcpCgojIGdldCBzdGFydCBwb2ludHMgKGxvbmdpdHVkZSBoYXMgdG8gZ28gZmlyc3QpCnN0YXJ0IDwtIGJpa2VzICU+JQogIHNlbGVjdChzdGFydF9sb25nLCBzdGFydF9sYXQpCmBgYAoKCmBgYHtyfQplbmQgPC0gYmlrZXMgJT4lIAogIHNlbGVjdChlbmRfbG9uZywgZW5kX2xhdCkKCmBgYAoKCgpgYGB7cn0KIyBjb252ZXJ0IHRvIG1hdHJpeCAgCnN0YXJ0X21hdHJpeCA8LSBhcy5tYXRyaXgoc3RhcnRbICwgYygxLDIpXSkKZW5kX21hdHJpeCA8LSBhcy5tYXRyaXgoZW5kWyAsIGMoMSwyKV0pCgojIHBsb3QgdXNpbmcgbGVhZmxldApnY0ludGVybWVkaWF0ZShzdGFydF9tYXRyaXgsIGVuZF9tYXRyaXgsICAKICAgICAgICAgICBuPTEwMCwgCiAgICAgICAgICAgYWRkU3RhcnRFbmQ9VFJVRSwKICAgICAgICAgICBzcD1UUlVFKSAlPiUgCmxlYWZsZXQoKSAlPiUgCmFkZFRpbGVzKCkgJT4lIAphZGRQb2x5bGluZXMoKSAKCmBgYAoK